package top.huntwolf.admin.utils;

import com.alibaba.fastjson.JSON;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
import com.wechat.pay.contrib.apache.httpclient.cert.CertificatesManager;
import com.wechat.pay.contrib.apache.httpclient.exception.HttpCodeException;
import com.wechat.pay.contrib.apache.httpclient.exception.NotFoundException;
import com.wechat.pay.contrib.apache.httpclient.exception.ParseException;
import com.wechat.pay.contrib.apache.httpclient.exception.ValidationException;
import com.wechat.pay.contrib.apache.httpclient.notification.Notification;
import com.wechat.pay.contrib.apache.httpclient.notification.NotificationHandler;
import com.wechat.pay.contrib.apache.httpclient.notification.NotificationRequest;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import top.huntwolf.admin.common.enums.CodeMsg;
import top.huntwolf.admin.common.enums.WXPayConstants;
import top.huntwolf.admin.exception.ServiceException;
import top.huntwolf.admin.pojo.wxPay.*;

import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.util.HashMap;
import java.util.Map;

@Component
public class WxPayUtil {


    @Value("${wechat.mchId}")
    private String merchantId;

    @Value("${wechat.mchSerialNo}")
    private String merchantSerialNumber;

    @Value("${wechat.apiV3Key}")
    private String apiV3Key;

    @Autowired
    PrivateKey merchantPrivateKey;

    /**
     * 获取证书
     * @return
     */
    private Verifier getVerifier(){

        // 获取证书管理器实例
        CertificatesManager certificatesManager = CertificatesManager.getInstance();

        // 向证书管理器增加需要自动更新平台证书的商户信息
        try {
            certificatesManager.putMerchant(merchantId, new WechatPay2Credentials(merchantId,
                    new PrivateKeySigner(merchantSerialNumber, merchantPrivateKey)), apiV3Key.getBytes(StandardCharsets.UTF_8));
        } catch (IOException e) {
            e.printStackTrace();
        } catch (GeneralSecurityException e) {
            e.printStackTrace();
        } catch (HttpCodeException e) {
            e.printStackTrace();
        }
        // 从证书管理器中获取verifier
        Verifier verifier = null;
        try {
            verifier = certificatesManager.getVerifier(merchantId);
        } catch (NotFoundException e) {
            e.printStackTrace();
        }
        return verifier;
    }

    private CloseableHttpClient getWxPayHttpClient() {

        Verifier verifier = getVerifier();

        WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
                .withMerchant(merchantId, merchantSerialNumber, merchantPrivateKey)
                .withValidator(new WechatPay2Validator(verifier));

        // 通过WechatPayHttpClientBuilder构造的HttpClient，会自动的处理签名和验签，并进行证书自动更新
        CloseableHttpClient httpClient = builder.build();

        return httpClient;
    }



    /**
     * 文档地址： https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_5_1.shtml
     * @param wxPayNotify
     * @return
     */
    public WxPrePay jsApi(WxPayJSAPI wxPayNotify) {
        HttpPost httpPost = new HttpPost(WXPayConstants.JSAPI_V3);
        httpPost.addHeader("Accept", "application/json");
        httpPost.addHeader("Content-type","application/json; charset=utf-8");
        CloseableHttpResponse response = null;
        WxPrePay wxPrePay = new WxPrePay();
        try {

            //设置请求体
            httpPost.setEntity(new StringEntity(JSON.toJSONString(wxPayNotify)));

            CloseableHttpClient httpClient = getWxPayHttpClient();

            //执行请求
            response = httpClient.execute(httpPost);

            String bodyAsString = EntityUtils.toString(response.getEntity());

            int code = response.getStatusLine().getStatusCode();

            if(code != 200){
                throw new ServiceException(CodeMsg.WXPAY_ERROR.fillArgs(bodyAsString));
            }

            wxPrePay = JSON.parseObject(bodyAsString,WxPrePay.class);

        }catch (ServiceException e) {
            throw e;
        }catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (response != null) {
                    response.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return wxPrePay;
    }

    /**
     * 关闭订单API
     * 文档地址：https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_5_3.shtml
     * @param orderId
     */
    public void closeOrder(String orderId){

        String url = "https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/"+orderId+"/close";
        HttpPost httpPost = new HttpPost(url);
        httpPost.addHeader("Accept", "application/json");
        httpPost.addHeader("Content-type","application/json; charset=utf-8");
        CloseableHttpResponse response = null;
        try {

            Map<String,String> map = new HashMap<>();
            map.put("mchid",merchantId);
            //设置请求体
            httpPost.setEntity(new StringEntity(JSON.toJSONString(map)));

            CloseableHttpClient httpClient = getWxPayHttpClient();

            //执行请求
            response = httpClient.execute(httpPost);

            String bodyAsString = EntityUtils.toString(response.getEntity());

            int code = response.getStatusLine().getStatusCode();

            if(code != 204){
                throw new ServiceException(CodeMsg.WXPAY_ERROR.fillArgs(bodyAsString));
            }
        }catch (ServiceException e) {
            throw e;
        }catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (response != null) {
                    response.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }


    }

    /**
     * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_5_9.shtml
     * @param refundRequest
     * @return
     */
    public WxPayRefundResult wxPayRefund(WxPayRefundRequest refundRequest) {
        HttpPost httpPost = new HttpPost(WXPayConstants.REFUNDS);
        httpPost.addHeader("Accept", "application/json");
        httpPost.addHeader("Content-type","application/json; charset=utf-8");
        CloseableHttpResponse response = null;
        WxPayRefundResult refundResult = new WxPayRefundResult();
        try {

            //设置请求体
            httpPost.setEntity(new StringEntity(JSON.toJSONString(refundRequest)));

            CloseableHttpClient httpClient = getWxPayHttpClient();

            //执行请求
            response = httpClient.execute(httpPost);

            String bodyAsString = EntityUtils.toString(response.getEntity());

            int code = response.getStatusLine().getStatusCode();

            if(code != 200){
                throw new ServiceException(CodeMsg.WXPAY_ERROR.fillArgs(bodyAsString));
            }

            refundResult = JSON.parseObject(bodyAsString,WxPayRefundResult.class);

        }catch (ServiceException e) {
            throw e;
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            try {
                if (response != null) {
                    response.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return refundResult;
    }

    /**
     * 查询单笔退款
     * 文档地址：https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_5_10.shtml
     * @param outRefundNo
     * @return
     */
    public WxPayRefundResult selectSingleRefund(String outRefundNo) {
        HttpGet httpGet = new HttpGet(WXPayConstants.SINGLE_REFUND + outRefundNo);
        httpGet.addHeader("Accept", "application/json");
        httpGet.addHeader("Content-type","application/json; charset=utf-8");
        CloseableHttpResponse response = null;
        WxPayRefundResult refundResult = new WxPayRefundResult();
        try {


            CloseableHttpClient httpClient = getWxPayHttpClient();

            //执行请求
            response = httpClient.execute(httpGet);

            String bodyAsString = EntityUtils.toString(response.getEntity());

            int code = response.getStatusLine().getStatusCode();

            if(code != 200){
                throw new ServiceException(CodeMsg.WXPAY_ERROR.fillArgs(bodyAsString));
            }

            refundResult = JSON.parseObject(bodyAsString,WxPayRefundResult.class);

        }catch (ServiceException e) {
            throw e;
        }catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (response != null) {
                    response.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return refundResult;
    }


    /**
     * 封装需要调起支付的信息
     * 文档位置:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_5_4.shtml
     */
    public Map<String,String> getWxPaySignRequest(String prepayId, String nonceStr){
        WxPayRequest wxPayRequest = new WxPayRequest();
        Environment environment = ApplicationContextUtil.get(Environment.class);
        wxPayRequest.setAppId(environment.getProperty("wechat.appid"));
        wxPayRequest.setTimeStamp(String.valueOf(System.currentTimeMillis() / 1000));
        wxPayRequest.setNonceStr(nonceStr);
        wxPayRequest.setSignType("RSA");
        wxPayRequest.setPrepayId("prepay_id="+prepayId);

        String paySign = getPaySign(wxPayRequest.getAppId(), wxPayRequest.getTimeStamp()
                , wxPayRequest.getNonceStr(), wxPayRequest.getPrepayId());

        wxPayRequest.setPaySign(paySign);

        Map<String,String> map = JSON.parseObject(JSON.toJSONString(wxPayRequest),Map.class);

        map.put("package",map.get("prepayId"));
        map.remove("prepayId");
        return map;

    }

    /**
     * 签名，使用字段appId、timeStamp、nonceStr、package计算得出的签名值
     * @return
     */
    private String getPaySign(String appId,String timeStamp,String nonceStr,String prepayId){

        String SignStr = String.format("%s\n%s\n%s\n%s\n",appId,timeStamp,nonceStr,prepayId);

        PrivateKey privateKey = null;
        try {
            privateKey = PemUtil.loadPrivateKey(
                    new FileInputStream("./path/to/apiclient_key.pem"));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }

        try {
            Signature signature = Signature.getInstance("SHA256withRSA");
            signature.initSign(privateKey);
            signature.update(SignStr.getBytes(StandardCharsets.UTF_8));
            return java.util.Base64.getEncoder().encodeToString(signature.sign());
        } catch (NoSuchAlgorithmException e) {
            throw new ServiceException(CodeMsg.NO_SUCH_ALGORITHM.fillArgs("SHA256withRSA"));
        } catch (InvalidKeyException e) {
            throw new ServiceException(CodeMsg.INVALID_KEY);
        } catch (SignatureException e) {
            throw new ServiceException(CodeMsg.SIGNATURE_ERROR);
        }


    }

    public <T> T notificationHandler(HttpServletRequest request, Type type) throws IOException {

        String nonce = request.getHeader("Wechatpay-Nonce");
        String timestamp = request.getHeader("Wechatpay-Timestamp");
        String signature = request.getHeader("Wechatpay-Signature");
        String wechatPaySerial = request.getHeader("Wechatpay-Serial");

        String body = getRequestBody(request);

        NotificationRequest notificationRequest = new NotificationRequest.Builder().withSerialNumber(wechatPaySerial)
                .withNonce(nonce)
                .withTimestamp(timestamp)
                .withSignature(signature)
                .withBody(body)
                .build();

        NotificationHandler handler = new NotificationHandler(getVerifier(), apiV3Key.getBytes(StandardCharsets.UTF_8));
        // 验签和解析请求体
        Notification notification = null;
        try {
            notification = handler.parse(notificationRequest);
        } catch (ValidationException e) {
            throw new ServiceException(CodeMsg.VALIDATION_ERROR);
        } catch (ParseException e) {
            throw new ServiceException(CodeMsg.PARSE_ERROR);
        }
        return JSON.parseObject(notification.getDecryptData(), type);
    }

    /**
     *
     * @param request
     * @return
     * @throws IOException
     */
    public static String getRequestBody(HttpServletRequest request) throws IOException {
        ServletInputStream stream = null;
        BufferedReader reader = null;
        StringBuilder sb = new StringBuilder();
        try {
            stream = request.getInputStream();
            if(stream != null){
                // 获取响应
                reader = new BufferedReader(new InputStreamReader(stream));
                String line;
                while ((line = reader.readLine()) != null) {
                    sb.append(line);
                }
            }else {
                sb.append("");
            }
        } catch (IOException e){
            throw new IOException("读取返回支付body发生异常！");
        } finally {
            reader.close();
        }
        return sb.toString();
    }


}
